/*
 * Copyright (c) 2005 - 2012 CAS Dev Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the CAS Dev. Team nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      ASCSRFModule.cpp
 *
 * $CAS$
 */


#include <ASDebugHelper.hpp>
#include <ASHandler.hpp>
#include <ASLoadableObject.hpp>
#include <ASMD5.hpp>
#include <ASUtil.hpp>

#include <ASMemcachedObject.hpp>

#include <sys/time.h>
#include <time.h>

#define C_CSRF_RL_KEY      "csrf_rl:"
#define C_CSRF_KEY         "csrf:"
#define C_RANDOM_DATA_LEN  16
#define C_X_REAL_IP_HEADER "X-Real-IP"

#define C_NO_ERROR               0
#define C_UNKNOWN_ERROR         -1
#define C_INTERNAL_SERVER_ERROR -2
#define C_SERVER_BUSY           -3
#define C_UNSUPPORTED_CALL      -4
#define C_FORBIDDEN             -5
#define C_INCORRECT_PARAMETER   -6
#define C_INCORRECT_VALUE       -7
#define C_INCORRECT_CALL_DATA   -8
#define C_NOT_SUPPORTED_YET     -9
#define C_RATE_LIMIT_EXCEED     -11

using namespace CAS;

namespace CAS_MOD
{
/**
  @class ASCSRFServer
  @brief Anti-Cross-site request forgery handler
*/
class ASCSRFServer:
  public ASHandler
{
public:
	/**
	  @brief A constructor
	*/
	ASCSRFServer();

	/**
	  @brief A destructor
	*/
	~ASCSRFServer() throw();

private:
	/**
	  @brief Request handler
	  @param oData - data model
	  @param oRequest - request object
	  @param oResponse - response object
	  @param oGlobalPool - global pool of objects
	  @param oVhostPool - virtual host pool
	  @param oLocationConfig - location configuration
	  @param pContextData - context data
	  @param oIMC - Inter-Module communication object
	  @param oLogger - application server logger
	  @return HANDLER_OK - if success, HANDLER_ERROR - if any error occured, HANDLER_DONE - if no futher processing need
	*/
	INT_32 Handler(CTPP::CDT   & oData,
	               ASRequest   & oRequest,
	               ASResponse  & oResponse,
	               ASPool      & oGlobalPool,
	               ASPool      & oVhostPool,
	               ASPool      & oRequestPool,
	               CTPP::CDT   & oLocationConfig,
	               ASObject    * pContextData,
	               CTPP::CDT   & oIMC,
	               ASLogger    & oLogger);

	/**
	  @brief Get handler name
	*/
	CCHAR_P GetObjectName() const;

	/**
	  @brief Create CSRF Token
	*/
	INT_32 CreateCSRFToken(ASRequest           & oRequest,
	                       ASPool              & oGlobalPool,
	                       CTPP::CDT           & oArguments,
	                       const CTPP::CDT     & oLocationConfig,
	                       const STLW::string  & sRemoteIP,
	                       CTPP::CDT           & oResponse,
	                       ASLogger            & oLogger);

	/**
	  @brief Create CSRF Token
	*/
	INT_32 CheckCSRFToken(ASRequest           & oRequest,
	                      ASPool              & oGlobalPool,
	                      CTPP::CDT           & oArguments,
	                      const CTPP::CDT     & oLocationConfig,
	                      const STLW::string  & sRemoteIP,
	                      CTPP::CDT           & oResponse,
	                      ASLogger            & oLogger);

	/**
	  @brief Create CSRF Token
	*/
	INT_32 DeleteCSRFToken(ASRequest           & oRequest,
	                       ASPool              & oGlobalPool,
	                       CTPP::CDT           & oArguments,
	                       const CTPP::CDT     & oLocationConfig,
	                       const STLW::string  & sRemoteIP,
	                       CTPP::CDT           & oResponse,
	                       ASLogger            & oLogger);
};

/**
  @class ASCSRFClient
  @brief Anti-Cross-site request forgery handler
*/
class ASCSRFClient:
  public ASHandler
{
public:
	/**
	  @brief A constructor
	*/
	ASCSRFClient();

	/**
	  @brief A destructor
	*/
	~ASCSRFClient() throw();

private:
	/**
	  @brief Request handler
	  @param oData - data model
	  @param oRequest - request object
	  @param oResponse - response object
	  @param oGlobalPool - global pool of objects
	  @param oVhostPool - virtual host pool
	  @param oLocationConfig - location configuration
	  @param pContextData - context data
	  @param oIMC - Inter-Module communication object
	  @param oLogger - application server logger
	  @return HANDLER_OK - if success, HANDLER_ERROR - if any error occured, HANDLER_DONE - if no futher processing need
	*/
	INT_32 Handler(CTPP::CDT   & oData,
	               ASRequest   & oRequest,
	               ASResponse  & oResponse,
	               ASPool      & oGlobalPool,
	               ASPool      & oVhostPool,
	               ASPool      & oRequestPool,
	               CTPP::CDT   & oLocationConfig,
	               ASObject    * pContextData,
	               CTPP::CDT   & oIMC,
	               ASLogger    & oLogger);

	/**
	  @brief Get handler name
	*/
	CCHAR_P GetObjectName() const;
};

EXPORT_HANDLER(ASCSRFServer)

EXPORT_HANDLER(ASCSRFClient)

//
// A constructor
//
ASCSRFServer::ASCSRFServer() { ;; }

//
// Get current time stamp
//
static W_FLOAT GetCurrentTimestamp()
{
	// Get timestamp
	struct timeval oTimeNow;
	gettimeofday(&oTimeNow, NULL);

return (oTimeNow.tv_sec + oTimeNow.tv_usec * 0.000001);
}

//
// Get remote IP
//
static STLW::string GetRemoteIP(const CAS::ASRequest & oRequest)
{
	STLW::string sRemoteIP = oRequest.headers.GetCDT(C_X_REAL_IP_HEADER).GetString();
	if (sRemoteIP.empty()) { sRemoteIP = oRequest.remote_ip; }

return sRemoteIP;
}

//
// Print error code
//
static void ReportError(const INT_32          iError,
                        const STLW::string  & sErrorDescr,
                        const STLW::string  & sRemoteIP,
                        CTPP::CDT           & oResult)
{
	using namespace CTPP;

	oResult = CDT(CDT::HASH_VAL);

	oResult["status"] = "ERROR";
	oResult["error"]["errno"]    = iError;
	oResult["error"]["strerror"] = sErrorDescr;

	oResult["partner"]["remote_ip"] = sRemoteIP;
}

//
// Get Memcached object from pool
//
static ASMemcachedObject * GetMemcachedObject(const STLW::string  & sConnectorName,
                                              CAS::ASPool         & oGlobalPool,
                                              const CTPP::CDT     & oLocationConfig,
                                              const STLW::string  & sRemoteIP,
                                              CTPP::CDT           & oResponse,
                                              CAS::ASLogger       & oLogger)
{
	// Get memcached prefix
	const STLW::string sMemcachedPrefix = oLocationConfig.GetCDT(sConnectorName).GetString();
	if (sMemcachedPrefix.empty())
	{
		ReportError(C_INTERNAL_SERVER_ERROR, "Memcached connector name is empty", sRemoteIP, oResponse);
		oLogger.Emerg("Memcached connector name is empty");
	}

	// Get memcached object
	ASMemcachedObject * pMemcached = oGlobalPool.GetResourceByName<ASMemcachedObject>(sMemcachedPrefix);
	if (pMemcached == NULL)
	{
		ReportError(C_INTERNAL_SERVER_ERROR, "Memcached object NOT found in GLOBAL pool", sRemoteIP, oResponse);
		oLogger.Emerg("Memcached object `%s`(`%s`) NOT found in GLOBAL pool", sConnectorName.c_str(), sMemcachedPrefix.c_str());
	}

return pMemcached;
}

//
// Leaky bucket algorithm
//
static INT_32 LeakyBucket(const W_FLOAT    dPerSecond,
                          const W_FLOAT    dBurst,
                          W_FLOAT        & dATime,
                          W_FLOAT        & dBucket,
                          const INT_32     iAttempts)
{
	// Time
	const W_FLOAT dTimeNow   = GetCurrentTimestamp();
	const W_FLOAT dTimeDiff  = dTimeNow - dATime;

	INT_32  iResult = 0;
	// Leaky bucked is on?
	if (dPerSecond != 0)
	{
		// Leak since last time we visited
		const W_FLOAT dTMP = (dTimeDiff * dPerSecond);

		if (dBucket > dTMP) { dBucket -= dTMP; }
		else                { dBucket = 0;     }

		// Bucket overflowed?
		if ((dBucket + iAttempts) > dBurst) { iResult = -1; }
		// Fill bucket
		else                                { dBucket += iAttempts; }
	}

	dATime = dTimeNow;

return iResult;
}

//
// Check rate limit
//
static INT_32 CheckRateLimit(const STLW::string  & sMethod,
                             ASMemcachedObject   * pMemcached,
                             const W_FLOAT         dPerSecond,
                             const W_FLOAT         dBurst,
                             const UINT_32         iEvents = 1)
{
	using namespace CTPP;

	STLW::string sKey(C_CSRF_RL_KEY);
	sKey.append(":");
	sKey.append(sMethod);

	INT_32 iRC = 0;

	CDT oResult;
	for (;;)
	{
		bool bSetKey = false;
		UINT_64 iCASUnique = 0;
		Memcached::eQueryResult oRC = pMemcached -> Gets(sKey, oResult, iCASUnique);

		if (oRC != Memcached::OK)
		{
			oResult = CDT(CDT::ARRAY_VAL);
			oResult[0] = 0;
			oResult[1] = 1;
			bSetKey = true;
		}

		W_FLOAT dATime  = oResult.GetCDT(0).GetFloat();
		W_FLOAT dBucket = oResult.GetCDT(1).GetFloat();

		iRC = LeakyBucket(dPerSecond, dBurst, dATime, dBucket, iEvents);

		oResult[0] = dATime;
		oResult[1] = dBucket;

		if (bSetKey)
		{
			pMemcached -> Set(sKey, oResult);
			break;
		}
		else
		{
			oRC = pMemcached -> Cas(sKey, oResult, iCASUnique, 0);
			if      (oRC == Memcached::OK) { break; }
			else if (oRC != Memcached::EXISTS)
			{
				// TBD: Error handling
				break;
			}
		}
	}

return iRC;
}

//
// Create CSRF Token
//
INT_32 ASCSRFServer::CreateCSRFToken(ASRequest           & oRequest,
                                     ASPool              & oGlobalPool,
                                     CTPP::CDT           & oArguments,
                                     const CTPP::CDT     & oLocationConfig,
                                     const STLW::string  & sRemoteIP,
                                     CTPP::CDT           & oResponse,
                                     ASLogger            & oLogger)
{
	using namespace CTPP;

	static CCHAR_P sAplahabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";

	const CDT & oParams = oArguments.GetCDT("params").GetCDT(0);

	// XML/JSON RPC method
	const STLW::string sMethod = oParams.GetCDT("method").GetString();
	// Get User Agent
	const STLW::string sUserAgent = oRequest.user_agent;

	ASMemcachedObject * pMemcached = GetMemcachedObject("CSRFMemcachedConnector", oGlobalPool, oLocationConfig, sRemoteIP, oResponse, oLogger);
	if (pMemcached == NULL) { return C_INTERNAL_SERVER_ERROR; }

	const CDT & oDefaultSettings = oLocationConfig.GetCDT("Default");

	CDT oMethodSettings = oLocationConfig.GetCDT(sMethod);
	oMethodSettings.MergeCDT(oDefaultSettings);

	const UINT_32 iExpirationTime = oMethodSettings.GetCDT("TTL").GetInt();
	const UINT_32 iPerSecond      = oMethodSettings.GetCDT("PerSecond").GetInt();
	const UINT_32 iBurst          = oMethodSettings.GetCDT("Burst").GetInt();

	MD5Hash oMethodPrefixHash;
	if (oMethodSettings.GetCDT("RateLimitByUserAgent").GetInt()) { oMethodPrefixHash.Update(sUserAgent); }
	if (oMethodSettings.GetCDT("RateLimitByIP").GetInt()) { oMethodPrefixHash.Update(sRemoteIP); }

/*
	const CDT & oExtraFields = oMethodSettings.GetCDT("ExtraField");
	if (oExtraFields.GetType() == CDT::ARRAY_VAL)
	{
		for (UINT_32 iPos = 0; iPos < oExtraFields.Size(); ++iPos)
		{
			oMethodPrefixHash.Update(oParams.GetCDT(oExtraFields[iPos].GetString()).GetString());
oLogger.Warn("`%s` -> `%s`", oExtraFields[iPos].GetString().c_str(), oParams.GetCDT(oExtraFields[iPos].GetString()).GetString().c_str());
		}
	}
	else if (oExtraFields.Size())
	{
		oMethodPrefixHash.Update(oParams.GetCDT(oExtraFields.GetString()).GetString());
	}
*/

	const STLW::string sMethodPrefix = sMethod  + ":" + oMethodPrefixHash.AsHex(true);

	// Check ratelimit for specified method
	if (CheckRateLimit(sMethodPrefix, pMemcached, iPerSecond, iBurst) != 0)
	{
		ReportError(C_RATE_LIMIT_EXCEED, "Rate limit exceed", sRemoteIP, oResponse);
		return C_RATE_LIMIT_EXCEED;
	}

	oLogger.Warn("Method: `%s`, TTL: %d, RL: %d BURST: %d", sMethodPrefix.c_str(), iExpirationTime, iPerSecond, iBurst);

	CHAR_8 sRandomData[C_RANDOM_DATA_LEN + 1];
	for (UINT_32 iPos = 0; iPos < C_RANDOM_DATA_LEN; ++iPos) { sRandomData[iPos] = sAplahabet[random() % 64]; }
	sRandomData[C_RANDOM_DATA_LEN] = 0;

	// Store context data
	CDT oCSRFContext;
	oCSRFContext.PushBack(sMethod);
	oCSRFContext.PushBack(sRemoteIP);
	oCSRFContext.PushBack(sUserAgent);
	oCSRFContext.PushBack(sRandomData);

	// Generate CSRF token
	const STLW::string sCSRFToken = MD5Hash().Update(sMethod).Update(sRandomData).Update(sUserAgent).Update(sRemoteIP).AsHex(true);

	STLW::string sCSRFKey(C_CSRF_KEY);
	sCSRFKey.append(sCSRFToken);

	// Store data
	pMemcached -> Set(sCSRFKey, oCSRFContext, iExpirationTime);

	oResponse["status"] = "OK";
	oResponse["token"]["result"] = "created";
	oResponse["token"]["key"]    = sCSRFToken;

return C_NO_ERROR;
}

//
// Check CSRF Token
//
INT_32 ASCSRFServer::CheckCSRFToken(ASRequest           & oRequest,
                                    ASPool              & oGlobalPool,
                                    CTPP::CDT           & oArguments,
                                    const CTPP::CDT     & oLocationConfig,
                                    const STLW::string  & sRemoteIP,
                                    CTPP::CDT           & oResponse,
                                    ASLogger            & oLogger)
{
	using namespace CTPP;

	const CDT & oParams = oArguments.GetCDT("params").GetCDT(0);

	// CSRF Token
	const STLW::string sCSRFToken = oParams.GetCDT("token").GetString();
	STLW::string sCSRFKey(C_CSRF_KEY);
	sCSRFKey.append(sCSRFToken);

	// XML/JSON RPC method
	const STLW::string sMethod = oParams.GetCDT("method").GetString();

	ASMemcachedObject * pMemcached = GetMemcachedObject("CSRFMemcachedConnector", oGlobalPool, oLocationConfig, sRemoteIP, oResponse, oLogger);
	if (pMemcached == NULL) { return C_INTERNAL_SERVER_ERROR; }

	// Get data
	CDT oCSRFContext;
	if (pMemcached -> Get(sCSRFKey, oCSRFContext) != Memcached::OK || oCSRFContext.GetType() != CDT::ARRAY_VAL)
	{
		ReportError(C_INCORRECT_VALUE, "Invalid CSRF token", sRemoteIP, oResponse);
		oLogger.Emerg("Invalid CSRF token `%s`", sCSRFToken.c_str());
	}

	const STLW::string sContextMethod = oCSRFContext.GetCDT(0).GetString();
	if (sContextMethod != sMethod)
	{
		ReportError(C_INCORRECT_VALUE, "Invalid method", sRemoteIP, oResponse);
		oLogger.Emerg("Invalid method: need `%s`, got `%s`", sMethod.c_str(), sContextMethod.c_str());
		return HANDLER_DONE;
	}

	oResponse["status"] = "OK";
	oResponse["token"]["result"] = "checked";
	oResponse["token"]["key"]    = sCSRFToken;

return C_NO_ERROR;
}

//
// Check CSRF Token
//
INT_32 ASCSRFServer::DeleteCSRFToken(ASRequest           & oRequest,
                                     ASPool              & oGlobalPool,
                                     CTPP::CDT           & oArguments,
                                     const CTPP::CDT     & oLocationConfig,
                                     const STLW::string  & sRemoteIP,
                                     CTPP::CDT           & oResponse,
                                     ASLogger            & oLogger)
{
	using namespace CTPP;

	const CDT & oParams = oArguments.GetCDT("params").GetCDT(0);

	// CSRF Token
	const STLW::string sCSRFToken = oParams.GetCDT("token").GetString();
	STLW::string sCSRFKey(C_CSRF_KEY);
	sCSRFKey.append(sCSRFToken);

	ASMemcachedObject * pMemcached = GetMemcachedObject("CSRFMemcachedConnector", oGlobalPool, oLocationConfig, sRemoteIP, oResponse, oLogger);
	if (pMemcached == NULL) { return C_INTERNAL_SERVER_ERROR; }

	// Delete token
	pMemcached -> Del(sCSRFKey);

	oResponse["status"] = "OK";
	oResponse["token"]["result"]    = "deleted";
	oResponse["token"]["key"]       = sCSRFToken;

return C_NO_ERROR;
}

//
// Initialize handler
//
INT_32 ASCSRFServer::Handler(CTPP::CDT   & oData,
                             ASRequest   & oRequest,
                             ASResponse  & oResponse,
                             ASPool      & oGlobalPool,
                             ASPool      & oVhostPool,
                             ASPool      & oRequestPool,
                             CTPP::CDT   & oLocationConfig,
                             ASObject    * pContextData,
                             CTPP::CDT   & oIMC,
                             ASLogger    & oLogger)
{
	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASCSRFServer::Handler");

	// Get method from IMC
	STLW::string sMethod = oIMC.GetCDT("method").GetString();
	if (sMethod.empty())
	{
		// Get method from request arguments
		sMethod = oRequest.arguments.GetCDT("method").GetString();
		if (sMethod.empty())
		{
			// Try to fetch from IMC
			oLogger.Warn("ASCSRFServer: Missed XML RPC method name!");
			return HANDLER_ERROR;
		}
	}

	// Get return code
	INT_32 iRC = HANDLER_DONE;
	if (oLocationConfig.GetCDT("DefaultReturnCode").GetString() == "HANDLER_OK") { iRC = HANDLER_OK; }

	// Execute calls

	// Create CSRF token
	if (sMethod == "XMLRPC::CSRF::create_token")
	{
		// 200 OK
		oResponse.SetHTTPCode(200);
		// Set HTTP header
		oResponse.SetHeader("X-Module", sMethod);

		const STLW::string sRemoteIP = GetRemoteIP(oRequest);

		CreateCSRFToken(oRequest, oGlobalPool, oRequest.arguments, oLocationConfig, sRemoteIP, oData, oLogger);

		return iRC;
	}

	// Check token
	if (sMethod == "XMLRPC::CSRF::check_token")
	{
		// 200 OK
		oResponse.SetHTTPCode(200);
		// Set HTTP header
		oResponse.SetHeader("X-Module", sMethod);

		const STLW::string sRemoteIP = GetRemoteIP(oRequest);

		CheckCSRFToken(oRequest, oGlobalPool, oRequest.arguments, oLocationConfig, sRemoteIP, oData, oLogger);

		return iRC;

	}

	// Delete token
	if (sMethod == "XMLRPC::CSRF::delete_token")
	{
		// 200 OK
		oResponse.SetHTTPCode(200);
		// Set HTTP header
		oResponse.SetHeader("X-Module", sMethod);

		const STLW::string sRemoteIP = GetRemoteIP(oRequest);

		DeleteCSRFToken(oRequest, oGlobalPool, oRequest.arguments, oLocationConfig, sRemoteIP, oData, oLogger);

		return iRC;
	}

	// Self test
	if (sMethod == "XMLRPC::CSRF::self_test")
	{
		oData["status"] = "OK";
		oData["token"]["result"] = "self_test_ok";

		return iRC;
	}

	// Can't find suitable handler, export list of available methods
	CDT & oMethods = oIMC["rpc"]["methods"];
	if (oMethods.GetType() != CDT::ARRAY_VAL) { oMethods = CDT(CDT::ARRAY_VAL); }
	oMethods.PushBack("XMLRPC::CSRF::create_token");
	oMethods.PushBack("XMLRPC::CSRF::check_token");
	oMethods.PushBack("XMLRPC::CSRF::delete_token");
	oMethods.PushBack("XMLRPC::CSRF::self_test");

return HANDLER_OK;
}

//
// Get handler name
//
CCHAR_P ASCSRFServer::GetObjectName() const { return "ASCSRFServer"; }

//
// A destructor
//
ASCSRFServer::~ASCSRFServer() throw() { ;; }

// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Class ASCSRFClient
//

//
// A constructor
//
ASCSRFClient::ASCSRFClient() { ;; }

//
// Initialize handler
//
INT_32 ASCSRFClient::Handler(CTPP::CDT   & oData,
                             ASRequest   & oRequest,
                             ASResponse  & oResponse,
                             ASPool      & oGlobalPool,
                             ASPool      & oVhostPool,
                             ASPool      & oRequestPool,
                             CTPP::CDT   & oLocationConfig,
                             ASObject    * pContextData,
                             CTPP::CDT   & oIMC,
                             ASLogger    & oLogger)
{
	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASCSRFClient::Handler");

	// Get method from IMC
	STLW::string sMethod = oIMC.GetCDT("method").GetString();
	if (sMethod.empty())
	{
		// Get method from request arguments
		sMethod = oRequest.arguments.GetCDT("method").GetString();
		if (sMethod.empty())
		{
			// Try to fetch from IMC
			oLogger.Warn("ASCSRFClient: Missed XML RPC method name!");
			return HANDLER_ERROR;
		}
	}

	// Check only specified methods
	bool bCDTExist = false;
	oLocationConfig.GetExistedCDT(sMethod, bCDTExist);
	if (!bCDTExist) { return HANDLER_OK; }

	// Set HTTP header
	oResponse.SetHeader("X-Module", sMethod);

	// Get remote IP
	const STLW::string sRemoteIP = GetRemoteIP(oRequest);
	// Get User Agent
	const STLW::string sUserAgent = oRequest.user_agent;

	ASMemcachedObject * pMemcached = GetMemcachedObject("CSRFMemcachedConnector", oGlobalPool, oLocationConfig, sRemoteIP, oData, oLogger);
	if (pMemcached == NULL) { return HANDLER_DONE; }

	const CDT & oParams = oRequest.arguments.GetCDT("params").GetCDT(0);

	// CSRF Token
	const STLW::string sCSRFToken = oParams.GetCDT("token").GetString();
	if (sCSRFToken.empty())
	{
		ReportError(C_FORBIDDEN, "Need CSRF token", sRemoteIP, oData);
		oLogger.Emerg("Can't get CSRF token from request");

		// Stop processing
		return HANDLER_DONE;
	}

	STLW::string sCSRFKey(C_CSRF_KEY);
	sCSRFKey.append(sCSRFToken);

	// Get data
	CDT oCSRFContext;
	if (pMemcached -> Get(sCSRFKey, oCSRFContext) != Memcached::OK || oCSRFContext.GetType() != CDT::ARRAY_VAL)
	{
		ReportError(C_FORBIDDEN, "Execution of this call is forbidden for you", sRemoteIP, oData);
		oLogger.Warn("Can't get memcached record for key `%s`", sCSRFToken.c_str());

		// Stop processing
		return HANDLER_DONE;
	}

	const STLW::string sContextMethod    = oCSRFContext.GetCDT(0).GetString();
	if (sContextMethod != sMethod)
	{
		ReportError(C_FORBIDDEN, "Execution of this call is forbidden for you", sRemoteIP, oData);
		oLogger.Emerg("CSRF: Invalid method name: need `%s`, got `%s`", sMethod.c_str(), sContextMethod.c_str());
		return HANDLER_DONE;
	}

	const STLW::string sContextRemoteIP  = oCSRFContext.GetCDT(1).GetString();
	if (sContextRemoteIP != sRemoteIP)
	{
		ReportError(C_FORBIDDEN, "Execution of this call is forbidden for you", sRemoteIP, oData);
		oLogger.Emerg("CSRF: Invalid IP address name: need `%s`, got `%s`", sRemoteIP.c_str(), sContextRemoteIP.c_str());
		return HANDLER_DONE;
	}

	const STLW::string sContextUserAgent = oCSRFContext.GetCDT(2).GetString();
	if (sContextUserAgent != sUserAgent)
	{
		ReportError(C_FORBIDDEN, "Execution of this call is forbidden for you", sRemoteIP, oData);
		oLogger.Emerg("CSRF: Invalid user agent: need `%s`, got `%s`", sUserAgent.c_str(), sContextUserAgent.c_str());
		return HANDLER_DONE;
	}

	// Delete token
	pMemcached -> Del(sCSRFKey);

	oIMC["csrf"]["result"] = "checked";

return HANDLER_OK;
}

//
// Get handler name
//
CCHAR_P ASCSRFClient::GetObjectName() const { return "ASCSRFClient"; }

//
// A destructor
//
ASCSRFClient::~ASCSRFClient() throw() { ;; }


} // namespace CAS_MOD
// End.

